/* -*- coding: utf-8 -*-
 * This file is part of Pathie.
 *
 * Copyright © 2015, 2017 Marvin Gülker
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef PATHIE_PATH_HPP
#define PATHIE_PATH_HPP
#include <string>
#include <iostream>
#include <vector>
#include <sys/stat.h>

#include "pathie.hpp"
#include "entry_iterator.hpp"

namespace Pathie {

  // Forward-declare, defined in pathie.cpp.
#if defined(_WIN32)
  std::string utf16_to_utf8(std::wstring);
  std::wstring utf8_to_utf16(std::string);
#elif defined(_PATHIE_UNIX)
  std::string utf8_to_filename(const std::string& utf8);
  std::string filename_to_utf8(const std::string& native_filename);
#endif

  /**
   * \brief Main class, describing paths.
   *
   * This class represents a single path on the filesystem.
   * The path does not have to exist, but this class provides
   * you with means to create it.
   *
   * Note on predefined directories
   * ------------------------------
   *
   * This class provides a lot of methods for retrieving information about
   * system and user predefined directories. Note however that the
   * referenced directories may or may not exist.
   *
   * See the pathlist.md document for an overview of possible path
   * return values.
   *
   * Note on XDG directories on UNIX
   * -------------------------------
   *
   * Nowadays UNIX systems have adapted the Freedesktop.org
   * XDG standards, and it is highly recommended to follow them
   * when you write an application that stores user-specific data.
   * XDG directories fall in two groups: Core data directories, covered
   * by the main XDG specification, and user-dir directories, described
   * in the documentation of the XDG user-dirs software. Directories of
   * the first group are available today on all Linux systems, examples
   * for them are ~/.config, ~/.local/share, and others. Directories
   * of the latter group are typically found on desktop systems and
   * are missing on servers, examples include ~/Documents and ~/Downloads.
   *
   * The following XDG specifications are followed:
   *
   * * XDG main specification: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
   * * XDG user-dirs specification: http://www.freedesktop.org/wiki/Software/xdg-user-dirs/
   *
   * Pathie is not a Shellscript parser, so it will fail if your XDG configuration
   * files do not follow the usually found format. Especially no other variable
   * substitution except from exactly one $HOME is understood.
   *
   * While the XDG specification for the core directories clearly says
   * which directory to use if the administrator/user has not specified
   * in his system configuration (by setting the appropriate environment
   * variables), the user-dirs isn’t that easy. Or rather, it is, but not
   * all desktop environment bother to follow it. The XDG user-dirs spec
   * requires a file `~/.config/user-dirs.dirs` to exist, generated by the
   * program xdg-user-dirs-update(1), which is run by all major desktop
   * environments. Smaller ones don’t always do that, resulting in the file
   * missing. The spec leaves open what should happen in such a case, i.e.
   * it’s implemention-defined behaviour. I have chosen to return the
   * user’s $HOME directory in such a case. The methods affected by this
   * decision are the following ones:
   *
   * * documents_dir()
   * * download_dir()
   * * music_dir()
   * * pictures_dir()
   * * publicshare_dir()
   * * templates_dir()
   * * videos_dir()
   *
   * Other notes
   * -----------
   *
   * On UNIX, this library follows the Filesystem Hierarchy Standard,
   * version 2.3 (http://refspecs.linuxfoundation.org/FHS_2.3/fhs-2.3.html).
   *
   * On UNIX, the FHS defines a "normal" file hierarchy and a "local" one; for
   * example, /usr/share is part of the "normal" file hierarchy, which is mirrored
   * to the "local" one in /usr/local/share. The "local" hierarchy is inteded to be
   * used by programs that the system administrator manually installed without resorting
   * to the system’s default package manager. Such a difference does not exist on Windows.
   * Pathie allows you to decide yourself which information you want to query when calling
   * one of the following functions:
   *
   * * global_mutable_data_dir()
   * * global_immutable_data_dir()
   * * global_config_dir()
   * * global_cache_dir()
   * * global_appentries_dir()
   *
   * Each of these functions takes an argument that allows you to specify whether
   * you want the "local" or the "normal" hierarchy’s paths returned. The argument
   * however is optional, and you can use the set_global_dir_default() method to
   * specify what should happen if no argument is specified. By default, paths of
   * the "local" hierarchy are returned. For example:
   *
   * ~~~~~~~~~~~~~~~~~~~ c++
   * Path p1 = Path::global_immutable_data_dir(); // /usr/local/share
   * Path p2 = Path::global_immutable_data_dir(Path::LOCALPATH_NORMAL); // /usr/share
   * Path p3 = Path::global_immutable_data_dir(Path::LOCALPATH_LOCAL); // /usr/local/share
   *
   * Path::set_global_dir_default(Path::LOCALPATH_NORMAL);
   * Path p4 = Path::global_immutable_data_dir(); // /usr/share
   * Path p5 = Path::global_immutable_data_dir(Path::LOCALPATH_LOCAL); // /usr/local/share
   * Path p6 = Path::global_immutable_data_dir(Path::LOCALPATH_NORMAL); // /usr/share
   * ~~~~~~~~~~~~~~~~~~~
   *
   * As you can see, the argument, if given, always takes precedence over the
   * default set with set_global_dir_default().
   */
  class Path
  {
  public:

    /**
     * Specifies the argument type for the `global_*_dir()` functions.
     * `LOCALPATH_DEFAULT` means fall back to the default set with `set_global_dir_default()`,
     * `LOCALPATH_NORMAL` means to use the normal FHS paths, and `LOCALPATH_LOCAL` means to use
     * the paths the FHS specifies for local additions.
     */
    enum localpathtype {
      LOCALPATH_DEFAULT = 1,
      LOCALPATH_NORMAL,
      LOCALPATH_LOCAL
    };

    /// Default constructor.
    Path();
    /// Copy constructor.
    Path(const Path& path);
    /// Construct a path from a string.
    Path(std::string path);
    /// Construct a path from components.
    Path(const std::vector<Path>& components);

#if defined(_PATHIE_UNIX)
    static inline Path from_native(const std::string& native_filename)
      { return Path(filename_to_utf8(native_filename)); }
#elif defined(_WIN32)
    /** Convert a path that is in the native representation of
     * the system into a Path instance. The argument will be
     * transcoded from the system’s native encoding to UTF-8;
     * on Windows, the argument is expected to be UTF-16LE therefore,
     * while on UNIX, it is expected to be encoded in the environment’s
     * locale. */
    static inline Path from_native(const std::wstring& native_filename)
      { return Path(utf16_to_utf8(native_filename)); }
#else
#error Unsupported system.
#endif

    /// Returns the current working directory.
    static Path pwd();
    /// Returns the path to the running executable.
    static Path exe();
    /// Returns the home directory.
    static Path home();

    static Path data_dir();        ///< Directory for permanent user data
    static Path config_dir();      ///< Directory for permanent user configuration files
    static Path cache_dir();       ///< Directory for cached user data
    static Path runtime_dir();     ///< Directory for volatile information
    static Path temp_dir();        ///< Directory for temporary data
    static Path desktop_dir();     ///< User’s desktop directory
    static Path documents_dir();   ///< User’s documents directory
    static Path download_dir();    ///< User’s download directory
    static Path music_dir();       ///< User’s music directory
    static Path pictures_dir();    ///< User’s pictures directory
    static Path publicshare_dir(); ///< User’s networking directory
    static Path templates_dir();   ///< User’s document templates directory
    static Path videos_dir();      ///< User’s video directory
    static Path appentries_dir();  ///< User’s application starters directory

    static Path global_mutable_data_dir(localpathtype local = LOCALPATH_DEFAULT);   ///< Global directory for immutable permanent data
    static Path global_immutable_data_dir(localpathtype local = LOCALPATH_DEFAULT); ///< Global directory for mutable permanent data
    static Path global_config_dir(localpathtype local = LOCALPATH_DEFAULT);         ///< Global directory for configuration files
    static Path global_cache_dir(localpathtype local = LOCALPATH_DEFAULT);          ///< Global directory for cached data
    static Path global_runtime_dir(localpathtype local = LOCALPATH_DEFAULT);        ///< Global directory for volatile information
    static Path global_appentries_dir(localpathtype local = LOCALPATH_DEFAULT);     ///< Global application starters directory
    static Path global_programs_dir();                        ///< Global directory for selfcontained programs

    static Path mktmpdir(const std::string& name = "tmpd"); ///< Create a temporary directory

    static inline void set_global_dir_default(localpathtype localdefault){ c_localdefault = localdefault; } ///< Specify what do do for the `global_*_dir()` methods if no argument is passed to them.
    static inline localpathtype get_global_dir_default(){ return c_localdefault; } ///< Returns what was set with set_global_dir_default().

#ifdef _PATHIE_UNIX
    static std::vector<Path> data_dirs();
    static std::vector<Path> config_dirs();
#endif

    /// Shell-like glob.
    static std::vector<Path> glob(const std::string& pattern, int flags = 0);
    /// Traverse directory recursively.
    void find(bool (*cb)(const Path& entry)) const;

    /// Return the path as a raw std::string.
    std::string str() const;
    /// Alias for str().
    std::string utf8_str() const;
    /// Assign the given string to the underlying path.
    void assign(std::string str);

#if defined(_PATHIE_UNIX)
    std::string native() const;
#elif defined(_WIN32)
    /// Return the path in the native format.
    std::wstring native() const;
#else
#error Unsupported system.
#endif

    void swap(Path& path) throw();

    /// Number of components in the path string.
    size_t component_count() const;
    /// Burst path into components.
    std::vector<Path> burst(bool descend = false) const;
    /// Shell-like globbing.
    std::vector<Path> dglob(const std::string& pattern, int flags = 0) const;
    /// Glob pattern check without filesystem access.
    bool fnmatch(const std::string& pattern, int flags = 0) const;

    Path& operator=(const Path& path);
    Path& operator=(const std::string& str);
    /// Access single component in the path.
    Path operator[](size_t index) const;
    bool operator==(const Path& path) const;
    bool operator!=(const Path& path) const;
    bool operator<(const Path& path) const;
    bool operator>(const Path& path) const;
    bool operator<=(const Path& path) const;
    bool operator>=(const Path& path) const;

    Path operator/(Path path) const;
    Path operator/(std::string str) const;
    Path& operator/=(Path path);
    Path& operator/=(std::string str);
    Path join(Path path) const;
    Path join(std::string path) const;
    Path sub_ext(std::string new_extension) const;

    /// Platform-independant C fopen().
    FILE* fopen(const char* mode) const;
    /// Update modification and access time to now.
    void touch() const;

    bool is_absolute() const; ///< Checks if a path is relative.
    bool is_relative() const; ///< Checks if a path is absolute.
    bool is_root() const;     ///< Checks if a path is the file system root.

    /// Remove all . and .. occurences.
    Path prune() const;
    /// Creates an absolute path for this path.
    Path absolute(const Path& base = Path::pwd()) const;
    /// Creates a relative path from an absolute one.
    Path relative(Path base) const;
    /// Expands all shortcuts plus create an absolute path for this path.
    Path expand() const;
    /// Get the one real path for this path.
    Path real() const;

    Path parent() const;
    Path root() const;
    Path basename() const;
    Path dirname() const;
    std::string extension() const;
    void split(Path& dirname, Path& basename) const;

    /// C stat information.
#if defined(_PATHIE_UNIX)
    struct stat* stat() const;
#elif defined(_WIN32)
    struct _stat* stat() const;
#else
#error Unsupported system.
#endif

    /// File size.
    long size() const;
    time_t atime() const;
    time_t mtime() const;
    time_t ctime() const;

    /// List of entries.
    std::vector<Path> entries() const;
    /// List of children.
    std::vector<Path> children() const;

    bool exists() const;
    bool is_directory() const;
    bool is_file() const;
    bool is_symlink() const;

    Path readlink() const;
    /// Create a symbolic link.
    void make_symlink(const Path& target) const;
    void mkdir() const;
    void rmdir() const;
    void unlink() const;
    void remove() const;
    /// "mkdir -p"-like functionality.
    void mktree() const;
    /// "rm -r"-link functionality.
    void rmtree() const;
    /// Change file names.
    void rename(Path& newname) const;

    entry_iterator begin_entries() const;
    entry_iterator end_entries() const;

  private:
    static std::string make_tempname(const std::string& namepart);
    // Remove double // and trailing /, replace \ with /.
    void sanitize();

#if defined(_PATHIE_UNIX)
    static Path get_xdg_dir(const std::string& envvarname, const std::string& defaultpath);
    static std::vector<Path> get_xdg_dirlist(const std::string& envvarname, const std::string& defaultlist);
    static std::string get_xdg_userdir_setting(const std::string& setting);
    static std::string get_home(std::string username);
#elif defined(_WIN32)
    bool is_ntfs_symlink(const wchar_t* path) const;
    wchar_t* read_ntfs_symlink(const wchar_t* path) const;
#endif

    static localpathtype c_localdefault;
    std::string m_path;
  };

}

/// std::cout compatibility.
std::ostream& operator<<(std::ostream& stream, const Pathie::Path& p);

#endif
